#!/usr/bin/env python
# -*- mode: python; coding: utf-8; -*-
#
# copyright (c) 2009 renat nasyrov <renatn@gmail.com>.
#
# this file is part of konverta4.
#
# konverta4 is free software: you can redistribute it and/or modify
# it under the terms of the gnu general public license as published by
# the free software foundation, either version 3 of the license, or
# (at your option) any later version.
#
# konverta4 is distributed in the hope that it will be useful,
# but without any warranty; without even the implied warranty of
# merchantability or fitness for a particular purpose.  see the
# gnu general public license for more details.
#
# you should have received a copy of the gnu general public license
# along with konverta4.  if not, see <http://www.gnu.org/licenses/>.

import cgi
import datetime
import hashlib
import logging
import os
import random
import urllib
import wsgiref.handlers

from google.appengine.ext import webapp
from google.appengine.ext.webapp import template
from google.appengine.api import urlfetch
from google.appengine.api import memcache
from exparser import ExecutionParser

USER_AGENT = "Konverta4/0.1"

class LoginException(Exception):

	def __init__(self, code):
		if code == 200:
			message = "Неправильное имя пользователя или пароль"
		elif code == 503:
			message = "503 Сервис временно не доступен"
		else:
			logging.error("HTTP 302 expected, but was %d", code)
			message = "Внутренняя ошибка сервера"
		Exception.__init__(self, message);


def get_4konverta_session(username, password, remember=""):
	""" Отправляет запрос авторизации на 4konverta.com,
		в случае успеха возвращает куки от сервиса, иначе
		вызывает исключение """

	post_fields = {
		"login": username,
		"password": password,
	}
	if remember != "":
		post_fields["remember"] = remember

	post_data = urllib.urlencode(post_fields)

	result = urlfetch.fetch(
			url="http://www.4konverta.com/login/process",
			payload=post_data,
			method=urlfetch.POST,
			headers={
				"Content-Type": "application/x-www-form-urlencoded",
				"User-Agent": USER_AGENT,
				},
			follow_redirects=False
			)

	if result.status_code != 302:
			raise LoginException(result.status_code)

	return result.headers['set-cookie']

def auth_required(func):
	""" Декоратор, проверяющий авторизацию пользователя,
		перед доступом к странице """

	def wrapper(self):
		session = self.get_session()
		if not session:
			self.redirect("/login")
		else:
			return func(self)
	return wrapper


class BaseHandler(webapp.RequestHandler):
	
	def get_sid(self):
		if self.request.cookies.has_key("sid"):
			return self.request.cookies["sid"]
		return ""

	def get_session(self):
		sid = self.get_sid()
		return memcache.get("sid-" + sid)

	def get_exec_key(self, monday):
		sid = self.get_sid()
		return "exec-%s-%s" % (sid, monday.strftime("%Y-%m-%d"))

	def error500(self, msg):
		logging.error(msg)
		self.error(500)
		self.response.out.write("<html><body><h2>%s</h2></body></html>" % msg)


class MainPage(BaseHandler):

	def get_execution_page(self, monday):
		""" Загружает HTML страницу "Выполнение" с 4konverta.com,
			вызывает исключение в случае неудачи. """

		cookies = self.get_session()
		response = urlfetch.fetch(
			url="http://www.4konverta.com/execution/"+monday.strftime("%Y-%m-%d"),
			headers={
				"Content-Type": "application/x-www-form-urlencoded",
				"User-Agent": USER_AGENT,
				"Cookie" : cookies,
				},
			)

		if response.status_code != 200:
			raise Exception("Сбой получения страницы 'Выполнение', статус: %d" % response.status_code)

		return response.content

	@auth_required
	def get(self):
		today = datetime.date.today()
		monday = today - datetime.timedelta(days=today.weekday())
		sunday = monday + datetime.timedelta(days=6)

		key = self.get_exec_key(monday)
		html = memcache.get(key)
		if html is None:
			try:
				html = self.get_execution_page(monday)
			except Exception, msg:
				self.error500(msg)
				return
				
			if not memcache.set(key=key, value=html, time=60):
				logging.error("Memcache set html failed")

		ep = ExecutionParser(monday)
		ep.feed(html)
		execution = ep.get_execution()
		budget = ep.get_budget()
		person_id = ep.get_person()
		byday = int(float(budget) / 7)

		template_values = {
			"execution" : execution,
			"monday" : monday,
			"sunday" : sunday,
			"week_budget" : budget,
			"day_budget" : byday,
			"person_id" : person_id,
		}
		path = os.path.join(os.path.dirname(__file__), "konvert.html")
		self.response.out.write(template.render(path, template_values))


class UpdateHandler(BaseHandler):

	@auth_required
	def post(self):
		expr = cgi.escape(self.request.get("expr"))
		key = cgi.escape(self.request.get("key"))
		monday = cgi.escape(self.request.get("mon"))

		post_fields = {
			"expr": expr.encode("utf-8"),
			"key": key,
		}
		post_data = urllib.urlencode(post_fields)
		
		cookies = self.get_session()

		result = urlfetch.fetch(
			url="http://www.4konverta.com/envelope/"+monday+"/update",
			payload=post_data,
			method=urlfetch.POST,
			headers={
				"Content-Type": "application/x-www-form-urlencoded",
				"User-Agent": USER_AGENT,
				"Cookie" : cookies,
				},
			follow_redirects=False
			)

		if result.status_code != 200:
			self.error500("Сбой во время сохранения дневных трат, статус : %d" % result.status_code)
			return

		# Remove cached page
		sid = self.get_sid()
		key = "exec-%s-%s" % (sid, monday)
		memcache.delete(key)

		self.redirect("/")

class LoginPage(BaseHandler):

	def new_sid(self):
		""" Генерирует новый Session ID """

		v = datetime.datetime.now().strftime("%s") + str(random.random())
		h = hashlib.md5()
		h.update(v)
		return h.hexdigest()

	def set_session(self, session, rmbr):
		""" Устанавливает куки с новым Session ID,
			и сохраняет сессию в memcache """
		
		sid = self.new_sid()
		t = 900
		if	rmbr != "":
			t = 3600*24*7

		expires = datetime.datetime.now() + datetime.timedelta(seconds=t)
		expires_rfc822 = expires.strftime('%a, %d %b %Y %H:%M:%S +0000')

		cookie = "sid=%s; path=/; expires=%s" % (sid, expires_rfc822)
		self.response.headers.add_header("Set-Cookie", cookie)

		if not memcache.set(key="sid-"+sid, value=session, time=t):
			logging.error("Memcache set session failed")

	def format_cookie(self, cookies):
		""" Удаляет из строки куки лишние параметры """

		t = map(lambda x: x.split(";")[0], cookies.split(","))
		t = filter(lambda x: x.find("=") != -1, t)
		return ";".join(t)

	def do_logout(self):
		""" Выполняет Logout пользователя из сервиса """

		cookies = self.get_session()
		sid = self.get_sid()
		memcache.delete("sid-" + sid)

		response = urlfetch.fetch(
			url="http://www.4konverta.com/logout",
			headers={
				"Content-Type": "application/x-www-form-urlencoded",
				"Cookie" : cookies,
				},
			)

		if response.status_code != 200:
			logging.error("Failed logout, status: %d", response.status_code)

	def get(self):
		if self.request.path == "/logout":
			self.do_logout()
			self.redirect("/login")
			return

		err = cgi.escape(self.request.get("err"))
		path = os.path.join(os.path.dirname(__file__), "login.html")
		self.response.out.write(template.render(path, {"error" : err}))

	def post(self):
		uname = cgi.escape(self.request.get("username"))
		passwd = cgi.escape(self.request.get("password"))
		rmbr = self.request.get("remember")

		try:
			cookies = get_4konverta_session(uname, passwd, rmbr)
		except LoginException, msg:
			self.redirect("/login?err=%s" % msg)
			return

		session = self.format_cookie(cookies)
		self.set_session(session, rmbr)
		self.redirect("/")

def main():
	application = webapp.WSGIApplication(
			[("/", MainPage),
			("/update", UpdateHandler),
			("/login", LoginPage),
			("/logout", LoginPage)],
			debug=True)
	wsgiref.handlers.CGIHandler().run(application)

if __name__ == "__main__":
	main()
